package cashpointer.mobile.support;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.Uri;
import cashpointer.mobile.R;
import cashpointer.mobile.dao.DAOFunctions;
import cashpointer.mobile.dao.DaoModel.Cashpoints;

/**
 * Watek uruchamiany, ktory w tle aktualizuje baze danych
 * @author wojtek
 *
 */
public class UpdateManager {
	// Logi
	//private static String LOG_TAG = "UpdateManager";
	
	/*
	private static Map<String, Integer> owners;
	private static Map<String, Integer> regions;

	static {
		owners = new HashMap<String, Integer>();
		regions = new HashMap<String, Integer>(); 
	}
	*/
	
	/**
	 * Funkcja aktualizujaca baze danych
	 */
	public static synchronized void update(Context context) {
		// Pobieram adres serwera
		String serverAddress = PreferencesManager.getString(context, PreferencesManager.SERVER_ADDRESS, context.getString(R.string.config_default_server));

		// Manadzer informujacy o polaczeniu z internetem
		ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
		
		// Jesli brak adresu przerywam aktualizacje
		if ("none".equals(serverAddress)) {
			// Komunikat problemu
			Functions.notifyUpdateProcess(context, context.getString(R.string.notification_update_no_server));
		} else {
			try {			
				// Pobieram obecna wersje bazy danych
				String currentVersion = PreferencesManager.getString(context, PreferencesManager.CURRENT_VERSION, context.getString(R.string.config_default_version));
				
				// Jesli wersja nie pasuje do wzorca oznacza to, ze trzeba sciagnac cala baze danych
				if (Pattern.matches("\\d{8}\\.\\d{3}", currentVersion)) {
					// Logi
					//Log.i(LOG_TAG, "Aktualizacja pomiedzy wersjami.");
					
					// Czytanie zawartosci strony
					String siteBody = Functions.readSite(serverAddress + "diff.html?version=" + currentVersion);

					// Usuwam zbedne znaki dla poprawnego parsowania xml
					siteBody = siteBody.replaceAll(">\\s+<", "><");

					// Pobranie adresu pliku i wersja danych
					DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
					factory.setIgnoringElementContentWhitespace(true);
					DocumentBuilder builder = factory.newDocumentBuilder();
					InputStream inputStream = new ByteArrayInputStream(siteBody.getBytes());
					Document parser = builder.parse(inputStream);

					// Pobieranie fragment
					Element cashpointersNode = parser.getDocumentElement();
					NodeList cashpointersNodeList = cashpointersNode.getChildNodes();
					
					// Pobranie wersji plikow
					HashMap<String, String> versions = new LinkedHashMap<String, String>();
					for (int i = 0; i < cashpointersNodeList.getLength(); i++) {
						Node fileNode = (Node) cashpointersNodeList.item(i);
						NodeList fileNodeList = fileNode.getChildNodes();
						
						// Ustalenie wersji
						Node versionNode = fileNodeList.item(0);
						Text versionText = (Text) versionNode.getFirstChild();
						String dataVersion = versionText.getData();
						
						// Ustalenie adresu
						Node urlNode = fileNodeList.item(1);
						Text urlText = (Text) urlNode.getFirstChild();
						String dataURL = urlText.getData();
						
						// Dodanie wersji do listy
						if (dataVersion != null && dataVersion.length() > 0 && dataURL != null && dataURL.length() > 0) {
							versions.put(dataVersion, dataURL);
						}
					}
			
					// Sortowanie pobranych danych
					HashMap<String, String> sortedVersions = new LinkedHashMap<String, String>();

					List<String> listOfKeys = new LinkedList<String>();
					
					for (String key : versions.keySet()) {
						listOfKeys.add(key);
					}
					
					Collections.sort(listOfKeys);
					
					for (String key : listOfKeys) {
						sortedVersions.put(key, versions.get(key));
					}

					// Przechodze po wersjach np. 20090416.016
					double procentOfProcess = 0.0;
					double procentOfStep = 100.0 / (double)sortedVersions.size();
					for (String version : sortedVersions.keySet()) {
						// Logi
						//Log.i(LOG_TAG, "Aktualizacja do wersji: " + version);
						
						// Testowanie polaczenia z internetem
						if (!Functions.isConnectionEnabled(connectivityManager)) {
							// Logi
							//Log.i(LOG_TAG, "Aktualizacja do wersji: " + version + " nie powiodla sie! Brak intenetu");
							
							// Komunikat problemu
							Functions.notifyUpdateProcess(context, context.getString(R.string.notification_update_no_connection));
							
							break;
						}
						
						if (version.endsWith(".000")) {
							// Usuwanie bankomatow
							try {
								// Logi
								//Log.i(LOG_TAG, "Usuwanie bankomatow z pliku o wersji: " + version);
								
								removeCachpointers(context, sortedVersions.get(version));
							} catch (Exception e) {
								// polykam wyjatek
								//Log.i(LOG_TAG, "Usuwanie bankomatow z pliku o wersji: " + version + " nie powiodlo sie!", e);
								break; // wychodzi z petli i przerywa aktualizacje
							}
						} else {
							// Dodawanie bankomatow
							try {
								// Logi
								//Log.i(LOG_TAG, "Dodawanie bankomatow z pliku o wersji: " + version);
								
								addCachpointers(context, sortedVersions.get(version));
								
								// Podsumowanie fragmentu aktualizacji
								procentOfProcess += procentOfStep;
								Functions.notifyUpdateProcess(context, "Zaktualizacja do wersji " + version + " w " + (int)procentOfProcess + "%");
							} catch (Exception e) {
								//Log.i(LOG_TAG, "Dodawanie bankomatow z pliku o wersji: " + version + " nie powiodlo sie!", e);
								break; // wychodzi z petli i przerywa aktualiacje
							}
						}
						
						// Po udanej aktualizacji zapisuje wersje
						PreferencesManager.setPreference(context, PreferencesManager.CURRENT_VERSION, version);

						// Logi
						//Log.i(LOG_TAG, "Zaktualizowano baze bankomatow do wersji: " + version);
						
						// Podsumowanie aktualizacji
						Functions.notifyUpdateProcess(context, "Zaktualizowano bazę bankomatów do wersji: " + version);
					}
				} else {
					// Logi
					//Log.i(LOG_TAG, "Pobieram cala nowa wersje.");
					
					// Adres pliku do pobrania i wersja
					String dataVersion = null;
					String dataURL = null;

					// Czytanie zawartosci strony i jej poprawienie jesli to konieczne
					String siteBody = Functions.readSite(serverAddress + "last.html");
					
					// Usuwam zbedne znaki dla poprawnego parsowania xml
					siteBody = siteBody.replaceAll(">\\s+<", "><");
					
					// Pobranie adresu pliku i wersja danych
					DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
					factory.setIgnoringElementContentWhitespace(true);
					DocumentBuilder builder = factory.newDocumentBuilder();
					InputStream inputStream = new ByteArrayInputStream(siteBody.getBytes());
					Document parser = builder.parse(inputStream);
					
					// Pobieranie wezla root
					Element cashpointersNode = parser.getDocumentElement();
					NodeList cashpointersNodeList = cashpointersNode.getChildNodes();
					
					// Pobranie wezla last
					Node lastNode = (Node) cashpointersNodeList.item(0);
					NodeList lastNodeList = lastNode.getChildNodes();
					
					// Usuniecie calej zawartosci bazy
					removeAllCashpointers(context);
					
					// Zmienne pomocnicze
					double procentOfProcess = 0.0;
					double procentOfStep = 100.0 / (double)lastNodeList.getLength();
					
					for (int i = 0; i < lastNodeList.getLength(); i++) {
						// Pobranie wezla file
						Node fileNode = (Node) lastNodeList.item(i);
						NodeList fileNodeList = fileNode.getChildNodes();
						
						// Ustalenie wersji
						Node versionNode = fileNodeList.item(0);
						Text versionText = (Text) versionNode.getFirstChild();
						dataVersion = versionText.getData();
						
						// Ustalenie adresu
						Node urlNode = fileNodeList.item(1);
						Text urlText = (Text) urlNode.getFirstChild();
						dataURL = urlText.getData();
						
						// Testowanie polaczenia z internetem
						if (!Functions.isConnectionEnabled(connectivityManager)) {
							// Logi
							//Log.i(LOG_TAG, "Aktualizacja do wersji: " + dataVersion + " nie powiodla sie! Brak intenetu");
							
							// Komunikat problemu
							Functions.notifyUpdateProcess(context, "Aktualizacja zakończona, brak połączenia z intenetem!");
							
							throw new Exception("Brak polaczenia z intenetem!");
						}
						
						try {
							// Logi
							//Log.i(LOG_TAG, "Aktualizacja do wersji: " + dataVersion);
							
							addCachpointers(context, dataURL);
							
							// Logi
							//Log.i(LOG_TAG, "Przetwarzono poprawnie: " + dataVersion);
							
							// Podsumowanie fragmentu aktualizacji
							procentOfProcess += procentOfStep;
							Functions.notifyUpdateProcess(context, "Zaktualizowano bazę bankomatów w " + (int)procentOfProcess + "%");
						} catch (Exception e) {
							//Log.e(LOG_TAG, "Wystapil blad podczas przetwarzania: " + dataVersion, e);
						}
					}
						
					// Po udanej aktualizacji zapisuje wersje
					PreferencesManager.setPreference(context, PreferencesManager.CURRENT_VERSION, dataVersion);

					// Logi
					//Log.i(LOG_TAG, "Zaktualizowano baze bankomatow do wersji: " + dataVersion);
					
					// Podsumowanie aktualizacji
					Functions.notifyUpdateProcess(context, "Zaktualizowano bazę bankomatów do wersji: " + dataVersion);
				} 
			} catch (Exception e) {
				// Logi
				//Log.e(LOG_TAG, "Aktualizacja zakonczona niepowodzeniem!!! " + e.getMessage(), e);
				
				// Komunikat problemu
				Functions.notifyUpdateProcess(context, "Aktualizacja zakończona niepowodzeniem!!!");
			}
		}
	}
	
	/**
	 * Funkcja pobiera dokument XML i zaczytuje do bazy danych wszystkie bankomaty z tego dokumentu
	 * @param string
	 * @throws Exception 
	 */
	private static void addCachpointers(Context context, String url) throws Exception {
		// Czytam zawartosc strony/xml
		String body = Functions.readSite(url);
		
		// Usuwam zbedne znaki dla poprawnego parsowania xml
		body = body.replaceAll(">\\s+<", "><");
		
		// Buduje parser do obrabiania dokumentu XML
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setIgnoringElementContentWhitespace(true);
		DocumentBuilder builder = factory.newDocumentBuilder();
		InputStream inputStream = new ByteArrayInputStream(body.getBytes());
		Document parser = builder.parse(inputStream);

		// Pobieranie fragment
		Element documentRoot = parser.getDocumentElement();
		NodeList cashpointsList = documentRoot.getChildNodes();
		
		// Logi
		//Log.i(LOG_TAG, "Ilosc nowych bankomatow do dodania: " + cashpointsList.getLength());
		
		// Przegladam wpisy o bankomatach
		for (int cashpointIndex = 0; cashpointIndex < cashpointsList.getLength(); cashpointIndex++) {
			// Logi
			//Log.i(LOG_TAG, "Przetwarzam : " + (cashpointIndex + 1) + " z " + cashpointsList.getLength());
			
			// Object-array
			Node objectArray = (Node) cashpointsList.item(cashpointIndex);
			NodeList objectArrayList = objectArray.getChildNodes();
			
			// Cashpoint
			Node cashpointNode = (Node) objectArrayList.item(0);
			NodeList cashpointNodeList = cashpointNode.getChildNodes();
			
			// Cashpoint::time
			String time = "";
			Node timeNode = (Node) cashpointNodeList.item(0);
			if (timeNode != null) {
				Text timeText = (Text) timeNode.getFirstChild();
				if (timeText != null) {
					time = timeText.getData();
					time = Functions.makePolish(time);
				}
			}
			
			// Cashpoint::address
			Node addressNode = (Node) cashpointNodeList.item(1);
			NodeList addressNodeList = addressNode.getChildNodes();
		
			// Cashpoint::address::city
			String city = null;
			Node cityNode = (Node) addressNodeList.item(0);
			if (cityNode != null) {
				Text cityText = (Text) cityNode.getFirstChild();
				if (cityText != null) {
					city = cityText.getData();
					city = Functions.makePolish(city);
				}
			}
			
			// Cashpoint::address::country
			String country = null;
			Node countryNode = (Node) addressNodeList.item(1);
			if (countryNode != null) {
				Text countryText = (Text) countryNode.getFirstChild();
				if (countryText != null) {
					country = countryText.getData();
					country = Functions.makePolish(country);
				}
			}
			
			// Cashpoint::address::region
			String region = null;
			Node regionNode = (Node) addressNodeList.item(2);
			if (regionNode != null) {
				Text regionText = (Text) regionNode.getFirstChild();
				if (regionText != null) {
					region = regionText.getData();
					region = Functions.makePolish(region);
				}
			}
			
			// Cashpoint::address::street
			String street = null;
			Node streetNode = (Node) addressNodeList.item(3);
			if (streetNode != null) {
				Text streetText = (Text) streetNode.getFirstChild();
				if (streetText != null) {
					street = streetText.getData();
					street = Functions.makePolish(street);
				}
			}
			
			// Cashpoint::address::info
			String info = "";
			Node infoNode = (Node) addressNodeList.item(4);
			if (infoNode != null) {
				Text infoText = (Text) infoNode.getFirstChild();
				if (infoText != null) {
					info = infoText.getData();
					info = Functions.makePolish(info);
				}
			}
			
			// Cashpoint::address::geoPoint
			Node geoPointNode = (Node) addressNodeList.item(5);
			NodeList geoPointNodeList = geoPointNode.getChildNodes();
			
			// Cashpoint::address::geoPoint::latitude
			String latitude = null;
			Node latitudeNode = (Node) geoPointNodeList.item(0);
			if (latitudeNode != null) {
				Text latitudeText = (Text) latitudeNode.getFirstChild();
				if (latitudeText != null) {
					latitude = latitudeText.getData();
				}
			}
			
			// Cashpoint::address::geoPoint::longitude
			String longitude = null;
			Node longitudeNode = (Node) geoPointNodeList.item(1);
			if (longitudeNode != null) {
				Text longitudeText = (Text) longitudeNode.getFirstChild();
				if (longitudeText != null) {
					longitude = longitudeText.getData();
				}
			}
			
			// Cashpoint::hashcode
			String hashcode = null;
			Node hashcodeNode = (Node) cashpointNodeList.item(2);
			if (hashcodeNode != null) {
				Text hashcodeText = (Text) hashcodeNode.getFirstChild();
				if (hashcodeText != null) {
					hashcode = hashcodeText.getData();
				}
			}

			// Owner
			Node ownerNode = (Node) objectArrayList.item(1);
			
			// Owner::name
			String owner = null;
			Node ownerNameNode = (Node) ownerNode.getFirstChild();
			if (ownerNameNode != null) {
				Text ownerNameText = (Text) ownerNameNode.getFirstChild();
				if (ownerNameText != null) {
					owner = ownerNameText.getData();
					owner = Functions.makePolish(owner);
				}
			}
			
			// Przetwarzam cashpoint
			if (time != null && 
				city != null && 
				country != null && 
				region != null && 
				street != null &&
				info != null &&
				latitude != null &&
				longitude != null &&
				hashcode != null
			) {
				// Sprawdzam, czy dany bankomat juz czasem nie istnieje
				int cashpointId = DAOFunctions.getCashpointId(context, hashcode);
				
				// Nowy bankomat wiec go dodaje
				if (cashpointId == 0) {
					try {
						// Dodaje/pobieram identyfikator ownera z bazy danych
						int ownerId = DAOFunctions.getOwner(context, owner);
						if (ownerId == 0) {
							throw new Exception("ownerId jest 0!");
						}
						
						// Dodaje/pobieram identyfikator wojewodztwa z bazy danych
						int regionId = DAOFunctions.getRegion(context, region);
						
						// Dodaje/pobieram identyfikator powiatu z bazy danych
						int countryId = DAOFunctions.getCountry(context, country, regionId);
						if (countryId == 0) {
							throw new Exception("countryId jest 0!");
						}
						
						// Buduje uproszczona nazwe miasta
						String simpleCity = Functions.makeSimple(city);
	
						// Buduje uproszczona nazwe ulicy
						String simpleStreet = Functions.makeSimple(street);
						
						// Przeinaczam szerokosc i wysokosc
						Double finalLatitude = Double.parseDouble(latitude.replaceAll(",", "."));
						Double finalLongitude = Double.parseDouble(longitude.replaceAll(",", "."));
						
						// Nowy bankomat
						ContentValues values = new ContentValues();
				        values.put(Cashpoints.TIME, time);
				        values.put(Cashpoints.ADDRESS_CITY, city);
				        values.put(Cashpoints.ADDRESS_CITY_SIMPLE, simpleCity);
				        values.put(Cashpoints.ADDRESS_INFO, info);
				        values.put(Cashpoints.ADDRESS_LATITUDE, finalLatitude);
				        values.put(Cashpoints.ADDRESS_LONGITUDE, finalLongitude);
				        values.put(Cashpoints.ADDRESS_STREET, street);
				        values.put(Cashpoints.ADDRESS_STREET_SIMPLE, simpleStreet);
				        values.put(Cashpoints.COUNTRY_ID, countryId);
				        values.put(Cashpoints.OWNER_ID, ownerId);
				        values.put(Cashpoints.HASHCODE, hashcode);
				        values.put(Cashpoints.MARKED, false);
				        
				        // Dodanie nowego bankomatu
				        context.getContentResolver().insert(Cashpoints.CONTENT_URI, values);
				        
				        // Logi
				        //Log.i(LOG_TAG, "Dodano nowy bankomat.");
					} catch (Exception e) {
						//Log.e(LOG_TAG, "Wystapil blad podczas dodawania bankomatu: " + e.getMessage());
					}
				} else {
					// Logi
			        //Log.i(LOG_TAG, "Bankomat juz istnieje!");
				}
			} else {
				// Logi
				/*
		        //Log.i(LOG_TAG, "Bankomat mial cos puste!");
		        //Log.i(LOG_TAG, "time: " + time);
		        //Log.i(LOG_TAG, "city: " + city);
		        //Log.i(LOG_TAG, "country: " + country);
		        //Log.i(LOG_TAG, "region: " + region);
		        //Log.i(LOG_TAG, "street: " + street);
		        //Log.i(LOG_TAG, "info: " + info);
		        //Log.i(LOG_TAG, "latitude: " + latitude);
		        //Log.i(LOG_TAG, "longitude: " + longitude);
		        //Log.i(LOG_TAG, "hashcode: " + hashcode);
		        */
			}
		}
	}

	/**
	 * Funkcja pobiera dokument XML i usuwa z bazy danych bankomaty wymienione na tej liscie
	 * @param string
	 * @throws Exception 
	 */
	private static void removeCachpointers(Context context, String url) throws Exception {
		// Czytam zawartosc strony/xml
		String body = Functions.readSite(url);
		
		// Usuwam zbedne znaki dla poprawnego parsowania xml
		body = body.replaceAll(">\\s+<", "><");

		// Buduje parser do obrabiania dokumentu XML
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setIgnoringElementContentWhitespace(true);
		DocumentBuilder builder = factory.newDocumentBuilder();
		InputStream inputStream = new ByteArrayInputStream(body.getBytes());
		Document parser = builder.parse(inputStream);

		// Pobieranie fragment
		Element documentRoot = parser.getDocumentElement();
		NodeList hashcodesList = documentRoot.getChildNodes();

		// Logi
		//int lengthOfHashcodeList = hashcodesList.getLength();
		////Log.i(LOG_TAG, "Ilosc bankomatow do usuniecia: " + lengthOfHashcodeList);		
		
		// Przegladam wpisy o bankomatach
		for (int hashcodeIndex = 0; hashcodeIndex < hashcodesList.getLength(); hashcodeIndex++) {
			try {
				// hashcode
				String hashcode = null;
				Node hashcodeNode = (Node) hashcodesList.item(hashcodeIndex);
				if (hashcodeNode != null) {
					Text hashcodeText = (Text) hashcodeNode.getFirstChild();
					if (hashcodeText != null) {
						hashcode = hashcodeText.getData();
					}
				}
				
				// Logi
				////Log.i(LOG_TAG, "Przetwarzam : " + (hashcodeIndex + 1) + " z " + lengthOfHashcodeList);
				
				// Pobieram identyfikator bankomatu
				int cashpointId = DAOFunctions.getCashpointId(context, hashcode);
				
				// Wyszukano bankomat
				if (cashpointId != 0) {
					// Buduje adres wybrnego bankomatu
			        Uri uri = ContentUris.withAppendedId(Cashpoints.CONTENT_URI, cashpointId);
			        
			        // Usuwam wybrany bankomat
			        context.getContentResolver().delete(uri, null, null);
			        
			        // Logi
			        //Log.i(LOG_TAG, "Usunieto bankomat: " + hashcode);
				} else {
					// Logi
					//Log.w(LOG_TAG, "Nie znaleziono bankomatu: " + hashcode);
				}
			} catch (Exception e) {
				// Logi
				//Log.e(LOG_TAG, "Wystapil blad! " + e.getMessage(), e);
			}
		}
	}
	
	/*
	private static int getOwner(Context context, String owner) {
		if (owners.containsKey(owner)) {
			return owners.get(owner);
		} else {
			int ownerId = DAOFunctions.getOwner(context, owner);
			owners.put(owner, new Integer(ownerId));
			return ownerId;
		}
	}

	private static int getRegion(Context context, String region) {
		if (regions.containsKey(region)) {
			return regions.get(region);
		} else {
			int regionId = DAOFunctions.getRegion(context, region);
			owners.put(region, new Integer(regionId));
			return regionId;
		}
	}
	*/
	
	/**
	 * Funkcja usuwajaca cala baze danych
	 */
	private static void removeAllCashpointers(Context context) {
        // Usuwam wszystkie bankomaty
		context.getContentResolver().delete(Cashpoints.CONTENT_URI, null, null);
        
        // Logi
        //Log.i(LOG_TAG, "Wszystkie bankomaty zostaly usuniete.");
	}	
}
